fix(project): Add polling fallback for file watching in containers - #1511
Draft
RandomByte wants to merge 1 commit into
Draft
fix(project): Add polling fallback for file watching in containers#1511RandomByte wants to merge 1 commit into
RandomByte wants to merge 1 commit into
Conversation
RandomByte
force-pushed
the
fix/fs-watch-in-containers
branch
3 times, most recently
from
August 11, 2026 11:43
6d87ab2 to
8aa2ff0
Compare
In many container setups (Docker over overlayfs, bind mounts, network filesystems) Linux inotify either does not deliver events or the native @parcel/watcher thread dies on EINTR: a SIGCHLD from a child process interrupts the blocking poll(), which the watcher treats as fatal (see parcel-bundler/watcher issue #253). In both cases subscribe() resolves and then goes silent, with no error to catch. The incremental build derives "what changed" solely from watcher events, so a silent watcher breaks rebuilds and live reload entirely. Parcel offers no polling subscription (its brute-force backend is query-only), so add one in a new module, pollingWatcher.js, that all three watcher consumers call instead of @parcel/watcher directly. It exposes a subscribe() matching @parcel/watcher's exact contract. The backend is chosen once per process and memoized: UI5_WATCH_MODE=polling|native forces it, otherwise a startup test subscribes natively to a temp dir on the same filesystem as the real watch, writes a file, and falls back to polling if no event arrives within 1500 ms. The polling path walks the tree and diffs an mtimeMs+size snapshot every 250 ms (rescheduling itself after each walk so a slow crawl cannot overlap the next), emitting the same {type, path} events the native backend would. Errors flow through the callback so each consumer's existing recovery path fires unchanged. The 250 ms interval stays below WATCHER_BURST_SETTLE_MS (550 ms) so downstream event batching still holds. Ignore globs reuse micromatch (already a direct dependency) and prune ignored directories so node_modules is never crawled. WatchHandler, ProjectDefinitionWatcher, and projectGraphSettleWatcher swap their parcelWatcher.subscribe call for the new module; callback bodies, ignore globs, and settle windows are untouched. UI5_WATCH_POLL_INTERVAL overrides the interval (clamped). A mid-run EINTR death after a passing startup test is not covered; force UI5_WATCH_MODE=polling in that case. Fixes: #1479
RandomByte
force-pushed
the
fix/fs-watch-in-containers
branch
from
August 11, 2026 11:51
8aa2ff0 to
60e08cb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In many container setups (Docker over overlayfs, bind mounts, network filesystems) Linux inotify either does not deliver events or the native @parcel/watcher thread dies on EINTR (a SIGCHLD from a child process interrupts the blocking poll(), which the watcher treats as fatal, see parcel-bundler/watcher#253). In both cases
subscribe()resolves and then stays silent, with no error to catch. The incremental build derives "what changed" solely from watcher events, so a silent watcher breaks rebuilds and live reload entirely.Parcel offers no polling subscription, so add one in a new
pollingWatcher.jsmodule that all three watcher consumers now call instead of@parcel/watcherdirectly. It exposes asubscribe()matching@parcel/watcher's exact contract. The backend is chosen once per process:UI5_WATCH_MODE=polling|nativeforces it, otherwise a startup test subscribes with the native watcher to a temp dir on the same filesystem as the real watch, writes a file, and falls back to polling if no event arrives within 1500 ms.The polling path walks the tree and diffs an mtimeMs+size snapshot every 250 ms (rescheduling itself after each walk so a slow crawl cannot overlap the next), emitting the same
{type, path}events the native watcher would. Errors flow through the callback so each consumer's existing recovery path fires unchanged. The 250 ms interval stays belowWATCHER_BURST_SETTLE_MS(550 ms) so downstream event batching still holds. Ignore globs reuse micromatch and prune ignored directories sonode_modulesis never crawled.WatchHandler,ProjectDefinitionWatcher, andprojectGraphSettleWatcherswap theirparcelWatcher.subscribecall for the new module; callback bodies, ignore globs, and settle windows are untouched.UI5_WATCH_POLL_INTERVALoverrides the interval (clamped).A mid-run EINTR death after a passing startup test is not covered; force
UI5_WATCH_MODE=pollingin that case.Fixes: #1479